/**
 *  mem.c - Memory management.
 *
 *  Copyright (C) 2008-2009 ZhangHu
 *  All rights reserved.
 *  E-MAIL: anmnmnly@gmail.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "include/mem.h"


static struct free_struct free_area[MEM_PAGE_LIST];
static page_t *mem; /* Pointer to start of memory */



/**
 * initmem - Initialize memory.
 * @return:
 *
 * @notes: All memory space are free at start time.
 *         and all space organized as one free block.
 */
void initmem(void) {
    uword_t i = 0;
    uword_t index = 1;
    page_t *page = NULL;

    for(i = 0; i < MEM_PAGE_LIST; i++) {
        free_area[i].free_link.next = &free_area[i].free_link;
        free_area[i].free_link.prev = &free_area[i].free_link;
        free_area[i].size = index * MEM_PAGE_SIZE;
        index *= 2;
    }

#ifdef UNIT_DEBUG
    page = (page_t*)malloc(MEM_SIZE * sizeof(char_t));
#else
	page = ((page_t*)MEM_START_ADDR);
#endif

    mem = page;
    page->free_link.prev = &page->free_link;
    page->free_link.next = &page->free_link;
    page->mem_up = NULL;
    page->mem_down = NULL;
    page->size = MEM_SIZE;
    page->status = MEM_FREE;
    add_node_list_rear(&free_area[MEM_PAGE_LIST - 1].free_link,
                       &page->free_link);
}



/**
 * get_start_address - get the start address of memory.
 * @return:
 *
 * @notes:
 */
word_t get_start_address(void) {
    return((word_t)mem);
}



/**
 * allocate - Assign a size of 'Size' space for application.
 * @return: The start address if successful, ohterwise NULL.
 *
 * @notes: This function is the main routine for assignment memory.
 *         however, applications do not called it directly.
 */
void *allocate(word_t Size) {
    list_t *plist = NULL;
    page_t *page = NULL;
    page_t *pleft = NULL;
    uword_t i = 0;
    uword_t flag = FALSE;
    uword_t tmp_size = 0;

    if(Size <= 0) {
        return(NULL);
    }

    tmp_size = Size + sizeof(page_t);

    /* Four-byte alignment */
    if((tmp_size & 0x03) != 0) {
        tmp_size >>= 0x2;
        tmp_size += 0x1;
        tmp_size <<= 0x02;
    }

    /* Find a free block */
    for(i = 0; i < MEM_PAGE_LIST; i++) {
        if((free_area[i].size < tmp_size) ||
           (free_area[i].free_link.next == (&free_area[i].free_link))) {
            continue;
        } else {
            plist = free_area[i].free_link.next;
            do {
                page = mac_find_entry(plist, page_t, free_link);
                if(page->size >= tmp_size) {
                    flag = TRUE;
                    del_node_list(&free_area[i].free_link, plist);
                    break;
                } else {
                    plist = plist->next;
                }
            } while(plist != free_area[i].free_link.next);
        }
        if(flag == TRUE) {
            break;
        }
    }
    /* Not found a free block space */
    if(flag != TRUE) {
        return(NULL);
    }
    /* Don't need split */
    if(page->size - tmp_size <= MEM_PAGE_LIMIT) {
        page->status = MEM_ALLOC;
        return(page + 1);
    }
    /* Divide this block space into two block, the one assigned to application,
       the other reserved */
    pleft = (page_t*)((char_t*)page + tmp_size);
    pleft->size = page->size - tmp_size;
    pleft->status = MEM_FREE;
    pleft->mem_up = page;
    pleft->mem_down = page->mem_down;

    if(page->mem_down != NULL) {
        page->mem_down->mem_up = pleft;
    }

    page->status = MEM_ALLOC;
    page->mem_down = pleft;
    page->size = tmp_size;

    /* inserting the new free memory block to free_arer list */
    for(i = 0; i < MEM_PAGE_LIST; i++) {
        if(free_area[i].size >= pleft->size) {
            add_node_list_rear(&free_area[i].free_link, &pleft->free_link);
            break;
        }
    }
    return(page + 1);
}



/**
 * reclaim - Reclaiming a block space.
 * @return:
 *
 * @notes: This function is the main routine for reclaim memory.
 *         however, applications do not called it directly.
 */
void reclaim(void *pfree) {
    uword_t i = 0;
    page_t *page = NULL;
    page_t *up = NULL;
    page_t *down = NULL;

    if((pfree == NULL) ||
       (((int)(pfree)) <= get_start_address())) {
        return;
    }

    /* get the page_t of reclaiming block */
    page = (((page_t*)(pfree)) - 1);

    if(page->status != MEM_ALLOC) {
        return;
    }

    page->status = MEM_FREE;
    up = page->mem_up;
    down = page->mem_down;

    /* combination with block that is free, and under of reclaiming block */
    if((down != NULL) && (down->status == MEM_FREE)) {
        for(i = 0; i < MEM_PAGE_LIST; i++) {
            if(free_area[i].size >= down->size) {
                del_node_list(&free_area[i].free_link, &down->free_link);
                break;
            }
        }
        page->size += down->size;
        down->size = 0;
        down->status = 0;
        down->mem_up = NULL;
        page->mem_down = down->mem_down;

        if(down->mem_down != NULL) {
            down->mem_down->mem_up = page;
            down->mem_down = NULL;
        }
    }

    /* combination with block that is free, and above of reclaiming block */
    if((up != NULL) && (up->status == MEM_FREE)) {
        for(i = 0; i < MEM_PAGE_LIST; i++) {
            if(free_area[i].size >= up->size) {
                del_node_list(&free_area[i].free_link, &up->free_link);
                break;
            }
        }
        up->size += page->size;
        page->size = 0;
        page->status = 0;
        page->mem_up = NULL;
        up->mem_down = page->mem_down;

        if(page->mem_down != NULL) {
            page->mem_down->mem_up = up;
            page->mem_down = NULL;
        }
        page = up;
    }

    /* inserting the new free memory block to free_arer list */
    for(i = 0; i < MEM_PAGE_LIST; i++) {
        if(free_area[i].size >= page->size) {
            add_node_list_rear(&free_area[i].free_link, &page->free_link);
            break;
        }
    }
}



/**
 * kmalloc - Assign a size of 'Size' space for application.
 *           The function is same as 'allocate()'
 * @return: The start address if successful, ohterwise NULL.
 *
 * @notes: This function designed for applications.
 *         so, applications shoule call it for assignment memory.
 */
void *kmalloc(word_t Size) {
    void *p = NULL;

    mac_disable_irq();
    p = allocate(Size);
    mac_enable_irq();
    return(p);
}



/**
 * kfree - Reclaiming a block space. the funtion is same as 'free()'.
 * @return:
 *
 * @notes: This function designed for applications.
 *         so, applications shoule call it for Reclaiming memory.
 */
void kfree(void *pfree) {
    if(pfree != NULL) {
        mac_disable_irq();
        reclaim(pfree);
        mac_enable_irq();
    }
}



/**
 * kmemset - Set all member of a buffer to 'ch'.
 * @buffer: Pointer to a buffer.
 * @ch: The value.
 * @count: The length of buffer.
 * @return:
 *
 * @notes:
 */
void kmemset(void *buffer, char_t ch, uword_t len) {
    char_t *p = (char_t*)buffer;

    if(p == NULL) {
        return;
    }

    while(len > 0) {
        *p++ = ch;
        len--;
    }
}



/**
 * kgetmemlist - Get the start address of memory.
 * @return: Start address of memory.
 *
 * @notes:
 */
page_t *kgetmemlist(void) {
    return(mem);
}

